English Русский Deutsch 日本語 한국어 Italiano Türkçe
preview
Application du filtrage de tendance L1 dans MetaTrader 5

Application du filtrage de tendance L1 dans MetaTrader 5

MetaTrader 5 — Exemples |
23 9
MetaQuotes
MetaQuotes
L’objectif principal du filtrage de tendance L1 est d’extraire la tendance sous-jacente d’une série temporelle de manière à ce que :
  • la dynamique à long terme des données soit préservée ;
  • les fluctuations et le bruit à court terme soient supprimés ;
  • les points de rupture structurels (changements de pente de la tendance) soient détectés automatiquement.
Contrairement aux méthodes de lissage classiques, cette approche n’impose pas de régularité à la tendance et produit à la place une approximation linéaire par morceaux, ce qui est particulièrement important pour l’analyse des séries temporelles financières.

Filtrage de tendance L1



Sommaire


Introduction

Les séries temporelles financières se caractérisent par un niveau de bruit élevé, des valeurs aberrantes fréquentes et des changements de régime de marché. Dans les systèmes de trading réels, cela se manifeste de manière simple et mesurable : les filtres « lisses » classiques (moyennes mobiles, HP) sont en retard, estompent les moments de changement de pente et interprètent souvent les corrections locales comme des retournements — en conséquence, le nombre de fausses entrées/sorties augmente, le Facteur de Profit diminue et le drawdown s’accroît. De plus, le choix du paramètre de régularisation λ se résume généralement à un réglage manuel et se transpose mal d’un instrument, d’une période ou d’une longueur d’historique à l’autre.

Cet article propose une solution pratique à ces problèmes, fondée sur le filtrage de tendance L1 : l’optimisation avec régularisation L1 des différences secondes produit automatiquement une approximation linéaire par morceaux avec des points de rupture explicites. Les principaux avantages sont une interprétation claire des points de rupture comme des changements de régime, la possibilité de fixer l’échelle de la régularisation grâce au calcul de λmax et au passage à un paramètre relatif λ = coef · λmax, ainsi qu’une complexité de calcul linéaire, adaptée à une implémentation en MQL5.

Nous présentons non seulement la théorie, mais aussi une feuille de route pratique complète : les méthodes de calcul de λmax et de la tendance L1, trois indicateurs (tendance, pente, signe de la pente), sept indicateurs de volatilité basés sur la tendance L1, l’intégration dans des Expert Advisors et un protocole de test reproductible (quatre modes de filtrage, export du solde et des fonds, et visualisation).


1. Formulation du problème de filtrage de tendance

Nous considérons une série temporelle scalaire représentée comme la somme de deux composantes :

, 

où est la composante de tendance et un bruit ou une composante irrégulière.

L’objectif est d’estimer la tendance ​ à partir des données observées ​.

Le problème peut être formulé comme un compromis entre la fidélité aux données d’origine et la régularité de la tendance estimée.


1.1. Filtre de Hodrick–Prescott

Le filtre de Hodrick–Prescott définit la tendance comme la solution du problème de minimisation :

,

où le paramètre λ contrôle le degré de lissage.

Principales propriétés du filtre HP :

  • Linéarité par rapport aux données ;
  • Complexité de calcul O(n) ;
  • Pour de petites valeurs de λ, la tendance se rapproche des données d’origine ;
  • Pour de grandes valeurs de λ, la tendance tend vers la meilleure approximation linéaire.

Cependant, le filtre HP produit toujours une tendance lisse et détecte mal les changements brusques de pente.


1.2. Méthode de filtrage de tendance L1

L’idée principale du filtrage de tendance L1 est de trouver une tendance proche des données d’origine, mais présentant le moins de changements de pente possible. Contrairement aux méthodes de lissage classiques, qui minimisent le carré de la courbure, l’approche L1 minimise la somme des valeurs absolues des différences secondes.

Cela aboutit à un résultat fondamentalement différent :

  • la plupart des différences secondes deviennent nulles,
  • la tendance est automatiquement divisée en segments linéaires.

Ainsi, le filtre L1 ne cherche pas à rendre la tendance lisse, mais trouve plutôt le nombre minimal de changements structurels qui expliquent les données observées. Cette méthode est donc particulièrement adaptée aux séries temporelles financières, dont la dynamique consiste souvent en des séquences de phases de croissance et de déclin quasi linéaires.

Dans le filtrage de tendance L1, la pénalité quadratique sur les différences secondes est remplacée par la norme L1, et la tendance est définie comme la solution d’un problème d’optimisation convexe :


Sous forme matricielle :


avec :

  • y — série temporelle d’entrée ;
  • x — la tendance estimée ;
  • D — matrice des différences secondes ;
  • λ>=0 — paramètre de régularisation.

L’utilisation de la norme L1 conduit à un résultat fondamentalement différent : de nombreuses différences secondes deviennent nulles, ce qui signifie que la tendance est linéaire par morceaux.

La différence seconde est définie comme suit :

Si , alors les points se trouvent sur une même droite.

Par conséquent, une différence seconde nulle correspond à un segment linéaire de la tendance, tandis qu’une différence seconde non nulle correspond à un point de rupture. La norme L1 favorise la parcimonie dans le vecteur Dx, ce qui signifie que la plupart des différences secondes deviennent nulles. Cela implique que, sur les intervalles correspondants, la tendance est linéaire. Les points où les différences secondes sont non nulles sont interprétés comme des points de rupture de tendance.

Ainsi, la méthode de filtrage de tendance L1 construit automatiquement la tendance sous la forme d’un ensemble de segments linéaires reliés aux points de changement structurel.

Principales propriétés du filtrage de tendance L1 :

    • La tendance se compose de segments linéaires ;
    • Les points de rupture sont interprétés comme des changements structurels de la série temporelle ;
    • À λ = 0, la tendance coïncide avec les données d’origine ;
    • Pour des valeurs de λ suffisamment grandes, la tendance devient exactement la meilleure approximation linéaire ;
    • La complexité de calcul reste linéaire par rapport au nombre d’observations.


    1.3. Rôle du paramètre de régularisation λ

    Le paramètre λ contrôle le compromis entre la précision de l’approximation et la complexité de la tendance :

    Valeur de λ Nature de la solution
    λ=0
    x=y, pas de lissage
    Petit λ
    Lissage faible, nombreux points de rupture
    λ moyen
    Tendance linéaire par morceaux
    Grand λ
    Tendance quasi linéaire
    λ≥λmax​
    Tendance strictement linéaire

    Tableau 1. Dépendance de la tendance L1 vis-à-vis du paramètre de régularisation λ

    Ainsi, λ contrôle le nombre et la position des points de rupture de la tendance.


    1.4. Interprétation géométrique du problème

    La tendance recherchée x peut être vue comme un point dans un espace à n dimensions. Le premier terme de la fonction objectif, responsable de la précision de l’approximation, définit une boule euclidienne centrée sur le point d’observation y : plus x est proche de y, plus l’erreur est faible.

    Le terme de régularisation avec la norme L1 des différences secondes définit un ensemble polyédrique convexe (polyèdre). Contrairement aux ellipsoïdes lisses qui apparaissent avec la régularisation L2, ce polyèdre possède des sommets saillants. Ces sommets correspondent à des situations où certaines différences secondes de la tendance sont égales à zéro.

    C’est précisément la présence de coins saillants dans la norme L1 qui conduit à des solutions parcimonieuses : la solution optimale tend à se situer sur un sommet du polyèdre, où seules certaines contraintes sont actives. Cela signifie que la plupart des différences secondes deviennent nulles et que la tendance prend automatiquement une forme linéaire par morceaux.

    La solution optimale correspond au premier point de contact entre la boule euclidienne et le polyèdre L1. En ce point, la tendance se compose de segments linéaires reliés par un nombre limité de points de rupture.

    Le paramètre λmax correspond à la situation où la boule euclidienne touche le polyèdre L1 non pas en un sommet, mais le long du sous-espace des fonctions linéaires. Dans ce cas, toutes les différences secondes sont nulles et la tendance est strictement linéaire.

    Pour λ ≥ λmax, aucune des contraintes L1 ne devient active ; une augmentation supplémentaire de la régularisation ne modifie donc pas la solution, et la tendance reste linéaire.

    1.5. Algorithme de calcul de λmax

    Considérons le calcul du paramètre de régularisation maximal λmax pour un vecteur d’entrée y de longueur N.

    1. Construisez la matrice des différences secondes D de taille (N−2)×N :


    2. Calculez le vecteur de courbure Dy.

    3. Résolvez le système d’équations linéaires :

    4. Prenez l’élément maximal (en valeur absolue) du vecteur v :

    Pour les séries temporelles financières, le paramètre λmax revêt une importance pratique considérable :

    • Il permet de normaliser le paramètre de régularisation ;
    • Il rend le choix de λ indépendant de l’échelle des données ;
    • Il simplifie la comparaison entre différentes séries temporelles ;
    • Il permet d’interpréter λ comme une fraction de la régularisation maximale.

    L’utilisation d’un paramètre relatif de la forme : λ=coef_lambda_max⋅λmax⁡, où coef_lambda_max ∈ (0,1), simplifie grandement l’application pratique.

    Dans les exemples d’indicateurs et d’Expert Advisors qui suivent, λ sera exprimé en unités de λmax, et les paramètres indiqueront le multiplicateur coef_lambda_max.


    2. Méthodes MQL5 de calcul de la tendance L1

    Pour une utilisation pratique du filtrage de tendance L1, deux méthodes sont implémentées pour les vecteurs de type double et float.

    • L1TrendFilterLambdaMax calcule le paramètre de régularisation maximal ;
    • L1TrendFilter calcule la tendance L1 pour une valeur donnée du paramètre de régularisation λ, qui peut également être spécifié en unités de λmax.

    2.1. L1TrendFilterLambdaMax

    Méthode de calcul du paramètre de régularisation maximal λmax pour un vecteur de données.

    Calcul pour vector<double> :

    bool  vector::L1TrendFilterLambdaMax(
       double          &lambda_max       // the maximum value of the regularization parameter lambda
       )
    Calcul pour vector<float> :
    bool  vectorf::L1TrendFilterLambdaMax(
       float           &lambda_max       // the maximum value of the regularization parameter lambda
       );

    Paramètres

    lambda

    [out] Valeur maximale du paramètre de régularisation λmax, ou -1 en cas d’erreur.

    Valeur de Retour

    Renvoie true en cas de succès.

    Note

    La consommation de mémoire augmente linéairement avec la taille du vecteur.


    2.2. L1TrendFilter

    Méthode de calcul de la tendance L1 pour un vecteur de données.

    Calcul pour 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
       );

    Calcul pour 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
       );

    Paramètres

    lambda

    [in] Valeur du paramètre de régularisation lambda (si relative = true, lambda est défini dans l’intervalle [0, 1] comme une fraction de λmax).

    relative

    [in] Booléen indiquant la façon dont λ est spécifié. Si true, λ est exprimé en unités de λmax ; sinon, la valeur absolue est utilisée.

    result

    [out] Vecteur contenant le résultat du filtrage L1.

    Valeur de Retour

    Renvoie true en cas de succès.

    Note

    La consommation de mémoire augmente linéairement avec la taille du vecteur.


    Plages recommandées pour λ (mode relatif).

    Multiplicateur de λ Résultat
    0,005 – 0,015 quasi L2, bruité
    0,02 – 0,04 micro-segments
    0,04 – 0,07 optimal pour les signaux
    0,07 – 0,12 tendances à moyen terme
    0,12 – 0,25 régimes de marché
    > 0,3 peu de segments

    Tableau 2. Plages de travail de λ en unités de λmax⁡


    Pour les applications pratiques, il est recommandé d’utiliser des multiplicateurs compris entre 0,04 et 0,25.



    3. Exemples d’application

    Dans cette section, nous examinons le calcul de la tendance L1 sur des données simulées de mouvement brownien et sur les cours du S&P 500, ainsi que les propriétés d’échelle de λmax pour le mouvement brownien et pour les données du marché Forex.

    Nous présentons également trois variantes d’indicateurs qui aident à déterminer les paramètres de régularisation optimaux (multiplicateurs de λmax) permettant d’obtenir la meilleure décomposition en tendance L1 pour des symboles et des périodes donnés.

    En outre, les résultats du filtrage des signaux de trading (alignement sur la tendance L1) sont présentés pour les stratégies MovingAverage, MACD, ADX et EMA.


    3.1. Calcul de la tendance L1 sur des données simulées (marche aléatoire)

    À titre d’exemple, considérons le calcul de la tendance L1 avec différentes valeurs du paramètre de régularisation λ sur des données simulées de mouvement brownien.

    Code du script :

    //+------------------------------------------------------------------+
    //|                                                  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();
      }
    //+------------------------------------------------------------------+
    
    Sortie :
    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
    

    Dans cet exemple, on constate que la diminution du paramètre de régularisation λ permet une décomposition plus détaillée en segments de tendance (Fig. 1).

    Si λ ≥ λmax, la solution devient une ligne droite correspondant à la régression linéaire (la tendance globale).


    Fig. 1. Exemple de calcul du filtre L1 avec différentes valeurs de λ sur des données de mouvement brownien

    Fig. 1. Exemple de calcul du filtre L1 avec différentes valeurs de λ sur des données de mouvement brownien


    Les fonctions de calcul de la tendance L1 sont disponibles pour les vecteurs double et float.

    Le script de test permettant de comparer les calculs est présenté ci-dessous.

    //+------------------------------------------------------------------+
    //|                                       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++;
         }
      }
    //+------------------------------------------------------------------+

    Sortie :


    3.2. Calcul de la tendance L1 pour la série des cours du S&P 500

    Prenons le calcul de log(S&P 500) tiré de l’article original l_1 Trend Filtering, S.J. Kim, K. Koh, S. Boyd et D. Gorinevsky, SIAM Review, section Problems and Techniques, 51(2):339–360, mai 2009. 

    Le script utilise les données du fichier « snp500.txt ». Ce fichier doit être placé dans le dossier : terminal_data_folder\MQL5\Files

    //+------------------------------------------------------------------+
    //|                                       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();
         }
      }
    //+------------------------------------------------------------------+
    

    Le résultat de l’exécution du script est présenté sur la Fig. 2.

    Fig. 2. Exemple d’estimation de la tendance L1 pour la série des logarithmes des cours de l’indice S&P 500

    Fig. 2. Exemple d’estimation de la tendance L1 pour la série des logarithmes des cours de l’indice S&P 500


    Dans l’onglet Experts, la valeur de λmax pour la série temporelle donnée sera affichée :

    TestL1TrendFilterSP500 (EURUSD,H1)      Lambda_max=37394.835512
    

    Ce script illustre l’utilisation des méthodes L1TrendFilterLambdaMax et L1TrendFilter avec une valeur fixe λ = 50, comme dans l’article original des auteurs de la méthode.

    Dans les exemples suivants, au lieu de valeurs absolues du paramètre de régularisation λ, nous utiliserons des valeurs relatives (en unités de λmax) avec le flag relative = true.


    3.3. Propriétés d’échelle de λmax⁡

    Le paramètre λmax joue un rôle clé dans le filtrage L1, car il définit la borne supérieure de la régularisation à partir de laquelle la solution dégénère en une approximation linéaire globale. Une propriété intéressante de cette grandeur est sa loi d’échelle en fonction de la longueur de la série temporelle.

    Des expériences numériques montrent que λmax croît selon une loi de puissance par rapport au nombre d’observations :

    avec : T — longueur de la série temporelle, α — exposant d’échelle.

    Pour une marche aléatoire (mouvement brownien), on peut montrer que l’exposant doit être proche de α ≈ 2,5. L’amplitude du mouvement brownien croît comme , tandis que l’opérateur de différence seconde évolue comme .  Le calcul de λmax revient en fait à évaluer le maximum d’une quantité liée à l’intégrale de la courbure de la série.


    Par conséquent, la combinaison de ces lois d’échelle conduit à la relation :

    ce qui correspond à un exposant α ≈ 2,5.

    Ainsi, à mesure que la longueur de la série temporelle augmente, la valeur de λmax croît beaucoup plus rapidement que de façon linéaire.


    3.3.1. Expérience numérique pour le mouvement brownien

    Pour vérifier la loi d’échelle, une expérience numérique a été menée.

    Pour différentes longueurs T de la série temporelle, des réalisations de mouvement brownien ont été générées, puis la valeur moyenne de λmax a été calculée.

    Une approximation logarithmique a été utilisée :

    ce qui permet d’estimer l’exposant α par régression linéaire.

    Le code de l’expérience est présenté ci-dessous.

    //+------------------------------------------------------------------+
    //|                            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();
      }
    //+------------------------------------------------------------------+

    Sortie :

    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      
    

    Le graphique en double échelle logarithmique montre une dépendance en loi de puissance de λmax vis-à-vis du nombre de points de données pour le mouvement brownien.
    .

    Fig. 3. Dépendance en loi de puissance de LambdaMax pour le mouvement brownien

    Fig. 3. Dépendance en loi de puissance de LambdaMax pour le mouvement brownien


    Les résultats de la simulation montrent :

    mean alpha = 2.4756
    std deviation = 0.036
    theory = 2.5
    percent error ≈ 1%

    L’expérience confirme donc la relation théorique :



    Le graphique en double échelle logarithmique montre une relation linéaire entre log(λmax) et log(T).


    3.3.2. Loi d’échelle pour les séries temporelles financières

    Une expérience similaire a été menée sur des séries de cours du marché Forex. L’exposant α a été estimé pour différentes paires de devises et périodes.

    Les résultats montrent que, pour des données financières réelles, la valeur de α se situe également dans l’intervalle α ≈ 2,45–2,60, ce qui est très proche de la valeur théorique du mouvement brownien. Cela signifie que le comportement d’échelle de λmax est quasi universel et se vérifie sur différents marchés et différentes périodes.

    Le script TestScalingLambdaMaxSymbol.mq5 calcule l’exposant de λmax pour un symbole donné sur les périodes standard 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);
      }
    //+------------------------------------------------------------------+
    

    Résultats pour 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  

    Les résultats pour EURUSD (périodes standard M1–H1) sont présentés sur la Fig. 4.

    Fig. 4. Dépendance en loi de puissance de λmax pour les différentes périodes de EURUSD

    Fig. 4. Dépendance en loi de puissance de λmax pour les différentes périodes de EURUSD

    De même, d’autres paires de devises peuvent être analysées.

    Pour 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  
    

    Les résultats pour USDJPY sont également bien approximés par une relation en loi de puissance.

    Fig. 5. Dépendance en loi de puissance de λmax pour les différentes périodes de USDJPY

    Fig. 5. Dépendance en loi de puissance de λmax pour les différentes périodes de USDJPY

    Pour 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  

    Une situation similaire est observée pour la série des cours de GBPUSD (Fig. 6).

    Fig. 6. Dépendance en loi de puissance de LambdaMax pour les différentes périodes de GBPUSD

    Fig. 6. Dépendance en loi de puissance de λmax pour les différentes périodes de GBPUSD


    Pour les séries EURUSD, USDJPY et GBPUSD considérées, les valeurs estimées de l’exposant sont également proches de 2,5.

    Les relations linéaires en échelle log-log de la fonction λmax sur plusieurs périodes et paires de devises indiquent une dépendance en loi de puissance de λmax vis-à-vis du nombre d’observations.


    3.3.3. Conséquences pratiques de la loi d’échelle

    L’existence d’une dépendance en loi de puissance pour λmax a une conséquence pratique importante.

    Puisque λmax ∝ T^2,5, la valeur absolue de λ dépend fortement :

    1. de la longueur de la fenêtre de données,
    2. de la période,
    3. de l’échelle de la série temporelle.

    Par conséquent, l’utilisation d’une valeur absolue de λ n’est pas pratique.

    Une approche beaucoup plus robuste consiste à utiliser un paramètre relatif λ=c⋅λmax, où 0<c<1.

    Une telle approche :

    • rend le paramètre de régularisation invariant par changement d’échelle,
    • simplifie le transfert des paramètres entre différents instruments,
    • permet d’utiliser les mêmes réglages sur différentes périodes.

    C’est pourquoi, dans tous les exemples suivants, le paramètre λ sera spécifié en unités de λmax.


    3.4. Indicateurs de tendance L1

    Dans cette section, trois types d’indicateurs sont examinés :

    • calcul de la tendance L1 à partir des cours de clôture ;
    • calcul des coefficients de croissance linéaire (pente) de la tendance L1 ;
    • calcul du signe de la pente de la tendance L1 ;
    Ces indicateurs peuvent être utilisés pour l’analyse visuelle de la décomposition en tendances et peuvent aider à déterminer les valeurs appropriées du paramètre de régularisation λ à utiliser dans les stratégies de trading.

    3.4.1. L1TrendFilter.mq5 - indicateur de tendance L1

    Dans cet exemple, le filtre L1 est calculé à partir des cours de clôture sur un nombre de barres donné (ici, BarsToShow = 1000), avec le coefficient lambda spécifié en unités de λmax.

    Le calcul utilise l’appel de méthode L1TrendFilter(relative = true), dans lequel le paramètre λ est défini en unités de λmax. Les valeurs de l’indicateur sont affichées directement dans la fenêtre du graphique.

    Le code de l’indicateur L1TrendFilter.mq5 est présenté ci-dessous.

    //+------------------------------------------------------------------+
    //|                                                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);
      }
    //+------------------------------------------------------------------+
    

    La Fig. 7 présente un exemple de calcul de l’indicateur L1TrendFilter.mq5 avec CoefLambda = 0.015.


    Fig. 7. Exemple de calcul de l’indicateur L1TrendFilter.mq5 avec CoefLambda = 0.015

    Fig. 7. Exemple de calcul de l’indicateur L1TrendFilter.mq5 avec CoefLambda = 0.015


    À titre de comparaison, on peut calculer plusieurs variantes avec différents paramètres de régularisation.

    La Fig. 8 montre les calculs avec les paramètres CoefLambda = 0.015, CoefLambda = 0.025 et CoefLambda = 0.055.


    Fig. 8. Exemples de calcul de l’indicateur L1TrendFilter.mq5 avec différentes valeurs de CoefLambda

    Fig. 8. Exemples de calcul de l’indicateur L1TrendFilter.mq5 avec différentes valeurs de CoefLambda



    3.4.2. L1TrendFilterSlope.mq5 - indicateur de la dynamique de la tendance L1

    Pour afficher la pente de la tendance, on peut utiliser l’incrément des valeurs de l’indicateur L1TrendFilter.

    Prenons par exemple l’indicateur L1TrendFilterSlope, qui affiche ses valeurs dans une fenêtre séparée.

    Le code de l’indicateur est présenté ci-dessous.

    //+------------------------------------------------------------------+
    //|                                           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);
      }
    //+------------------------------------------------------------------+
    

    Le résultat de l’utilisation conjointe des indicateurs L1TrendFilter.mq5 et L1TrendFilterSlope.mq5 est présenté sur la Fig. 9.

    Fig. 9. Exemple de calcul des indicateurs L1TrendFilter.mq5 et L1TrendFilterSlope.mq5 avec CoefLambda = 0.015

    Fig. 9. Exemple de calcul des indicateurs L1TrendFilter.mq5 et L1TrendFilterSlope.mq5 avec CoefLambda = 0.015


    3.4.3. L1TrendFilterSlopeSign.mq5 - indicateur de la direction de la tendance L1

    De même, on peut calculer un indicateur qui affiche le signe de l’incrément de l’indicateur L1TrendFilterSlope.mq5.

    Code de l’indicateur 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);
      }
    //+------------------------------------------------------------------+
    

    Un exemple d’utilisation conjointe des trois indicateurs est présenté sur la Fig. 10 (avec la même valeur de coefficient CoefLambda = 0.015).

    Fig. 10. Exemple de calcul des indicateurs L1TrendFilter.mq5, L1TrendFilterSlope.mq5 et L1TrendFilterSlopeSign.mq5 avec CoefLambda = 0.015

    Fig. 10. Exemple de calcul des indicateurs L1TrendFilter.mq5, L1TrendFilterSlope.mq5 et L1TrendFilterSlopeSign.mq5 avec CoefLambda = 0.015




    3.4.4. Indicateurs de volatilité basés sur la tendance L1

    Cette section présente des indicateurs conçus pour évaluer la volatilité d’un instrument financier à partir de la tendance L1.

    Ces outils permettent d’identifier les périodes d’instabilité et de stabilité du marché, d’analyser la dynamique actuelle du marché et de prendre des décisions de trading plus éclairées.

    Les indicateurs présentés dans cette section sont les suivants :

    • L1Volatility.mq5 — volatilité résiduelle par rapport à la tendance L1 ;
    • L1VolatilitySmoothed.mq5 — volatilité résiduelle lissée ;
    • L1VolatilityAbsolute.mq5 — volatilité absolue ;
    • L1VolatilityNormalized.mq5 — volatilité normalisée ;
    • L1VolatilityNormalizedSmoothed.mq5 — volatilité normalisée lissée ;
    • L1VolatilityRegime.mq5 — détection du régime de marché basée sur la volatilité.

    Tous les indicateurs reposent sur un cadre commun fondé sur la tendance L1, ce qui garantit la cohérence de l’analyse et simplifie l’interprétation des résultats obtenus.

    Ces indicateurs permettent d’identifier visuellement les périodes de forte et de faible volatilité, ainsi que de déterminer le régime de marché actuel : consolidation, tendance, expansion ou panique.

    Un trader peut ainsi adapter ses stratégies de trading aux conditions de marché du moment, par exemple en appliquant des approches plus prudentes pendant les périodes de faible volatilité ou des stratégies plus actives lors de forts mouvements du marché.


    3.4.4.1. L1Volatility.mq5 — indicateur de volatilité L1

    L’indicateur calcule la volatilité résiduelle comme la différence entre les cours de clôture et la valeur correspondante de la tendance L1.

    Cette approche permet d’identifier les périodes d’instabilité du marché et les moments précis d’entrée ou de sortie.

    Visuellement, l’indicateur est affiché dans une fenêtre séparée du graphique, sous la forme d’une ligne orange.

    L’indicateur permet de :

    • évaluer les écarts des cours par rapport à la tendance L1 et mesurer la force du mouvement du marché ;
    • détecter les pics de volatilité locaux pour une gestion des risques plus précise ;
    • comparer la dynamique de différents instruments sur la même période.

    L’indicateur est particulièrement utile dans les systèmes où il est nécessaire de surveiller les variations de volatilité à court terme sans perdre de vue le contexte de la tendance générale.

    Le code de l’indicateur L1Volatility.mq5 est présenté ci-dessous.

    //+------------------------------------------------------------------+
    //|                                                 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);
      }
    //+------------------------------------------------------------------+
    

    Le résultat du calcul est présenté sur la Fig. 11.


    Fig. 11. Indicateur L1Volatility.mq5

    Fig. 11. Indicateur L1Volatility.mq5



    3.4.4.2. L1VolatilitySmoothed.mq5 — indicateur de volatilité résiduelle lissée

    Cet indicateur est une version lissée de L1Volatility, à laquelle une moyenne mobile simple (Simple Moving Average, SMA) est appliquée.

    Le lissage permet :

    • de réduire le bruit à court terme et les valeurs aberrantes ;
    • d’obtenir une visualisation plus claire et plus facile à interpréter ;
    • de se concentrer sur les changements persistants de la volatilité.

    L’indicateur est utile pour les stratégies qui nécessitent d’évaluer les tendances de la volatilité sur une plus longue période, par exemple dans les systèmes de trading adaptatifs ou pour filtrer les faux signaux pendant les phases de tendance et de consolidation du marché.

    Le code de l’indicateur L1VolatilitySmoothed.mq5 est présenté ci-dessous.

    //+------------------------------------------------------------------+
    //|                                         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);
      }
    //+------------------------------------------------------------------+
    

    La Fig. 12 montre les indicateurs L1Volatility.mq5 et L1VolatilitySmoothed.mq5.

    Fig. 12. Indicateurs L1Volatility.mq5 et L1VolatilitySmoothed.mq5

    Fig. 12. Indicateurs L1Volatility.mq5 et L1VolatilitySmoothed.mq5



    3.4.4.3. L1VolatilityAbsolute.mq5 — indicateur de volatilité absolue

    L’indicateur calcule la valeur absolue de la différence entre les cours de clôture et la tendance L1.

    Caractéristiques et applications :

    • ignore la direction du mouvement et n’évalue que l’amplitude des fluctuations ;
    • pratique pour analyser l’amplitude des oscillations des cours indépendamment de la direction de la tendance ;
    • utile pour les systèmes basés sur les statistiques des valeurs extrêmes et l’analyse des risques.

    La volatilité absolue reflète l’ampleur réelle des écarts de cours, ce qui permet au trader d’observer la force du mouvement du marché sans se laisser distraire par sa direction.

    Le code de l’indicateur L1VolatilityAbsolute.mq5 est présenté ci-dessous.

    //+------------------------------------------------------------------+
    //|                                         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);
      }
    //+------------------------------------------------------------------+
    

    Un exemple de calcul de l’indicateur est présenté sur la Fig. 13.


    Fig. 13. Indicateur L1VolatilityAbsolute.mq5

    Fig. 13. Indicateur L1VolatilityAbsolute.mq5


    3.4.4.4. L1VolatilityNormalized.mq5 — indicateur de volatilité normalisée

    L’indicateur normalise la volatilité à l’aide de l’ATR (Average True Range) combiné à la tendance L1.

    Il calcule le rapport entre l’écart absolu du cours par rapport à la tendance et l’amplitude moyenne des cours sur la période de l’ATR. La normalisation supprime la dépendance à l’échelle des cours, ce qui permet de comparer différents instruments et différentes périodes.

    Applications :

    • identification des mouvements de marché relativement forts et faibles ;
    • comparaison de la volatilité entre différents actifs ;
    • évaluation des conditions de marché indépendamment du niveau des cours.

    Le code de l’indicateur L1VolatilityNormalized.mq5 est présenté ci-dessous.

    //+------------------------------------------------------------------+
    //|                                       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);
      }
    //+------------------------------------------------------------------+
    

    Le résultat du calcul est présenté sur la Fig. 14.

    Fig. 14. Indicateur L1VolatilityNormalized.mq5

    Fig. 14. Indicateur L1VolatilityNormalized.mq5


      3.4.4.5. L1VolatilityNormalizedSmoothed.mq5 — indicateur de volatilité normalisée lissée

      Cet indicateur prolonge l’approche par normalisation en y ajoutant un lissage par moyenne mobile exponentielle (Exponential Moving Average, EMA).

      Avantages :

      • réduit l’influence du bruit à court terme et des pics brusques ;
      • produit un profil de volatilité plus clair et plus facile à interpréter ;
      • aide à évaluer la volatilité persistante et le régime de marché actuel.

      L’indicateur est particulièrement utile pour les stratégies adaptatives qui nécessitent une estimation stable de la volatilité, par exemple pour la sélection automatique des modes de trading.

      Le code de l’indicateur L1VolatilityNormalizedSmoothed.mq5 est présenté ci-dessous.

      //+------------------------------------------------------------------+
      //|                               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);
        }
      //+------------------------------------------------------------------+
      

      Le résultat du calcul est présenté sur la Fig. 15.

      Fig. 15. Indicateurs L1VolatilityNormalized.mq5 et L1VolatilityNormalizedSmoothed.mq5

      Fig. 15. Indicateurs L1VolatilityNormalized.mq5 et L1VolatilityNormalizedSmoothed.mq5



      3.4.4.6. L1VolatilityRegime.mq5 — indicateur de détection du régime de marché

      L’indicateur classe le régime de marché actuel à partir de la volatilité normalisée et lissée, en distinguant quatre états du marché.

      Caractéristiques de l’indicateur :

      • entièrement autonome, il ne nécessite aucune donnée externe ;
      • fournit une visualisation claire de la dynamique du marché pour les stratégies adaptatives ;
      • les paramètres de seuil LowVolThresh et HighVolThresh peuvent être ajustés selon les instruments et les périodes.
      Valeur Régime Description
      0 Consolidation
      Faible volatilité, marché latéral
      1 Tendance
      Volatilité modérée, présence d’une tendance
      2 Expansion
      Mouvement fort, expansion du marché
      3 Panique Volatilité extrême, mouvements brusques

      Tableau 3. Régimes de l’indicateur L1VolatilityRegime.mq5

      • déterminer rapidement le régime de marché actuel ;
      • adapter les stratégies de trading aux conditions du moment ;
      • réduire le risque lors des mouvements extrêmes et améliorer l’efficacité du trading.

      Le code de l’indicateur L1VolatilityRegime.mq5 est présenté ci-dessous.

      //+------------------------------------------------------------------+
      //|                                           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);
        }
      //+------------------------------------------------------------------+
      

      Le résultat du calcul est présenté sur la Fig. 16.

      Fig. 16. Indicateur L1VolatilityRegime.mq5

      Fig. 16. Indicateur L1VolatilityRegime.mq5

        Pour plus de commodité, on peut également utiliser une version qui affiche les régimes avec un code couleur.

        Le code de l’indicateur L1VolatilityRegimeColor.mq5 est présenté ci-dessous.

        //+------------------------------------------------------------------+
        //|                                      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);
          }
        //+------------------------------------------------------------------+
        

        Le résultat du calcul combiné est présenté sur la Fig. 17.

        Fig. 17. Indicateurs L1VolatilityRegime.mq5 et L1VolatilityRegimeColor.mq5

        Fig. 17. Indicateurs L1VolatilityRegime.mq5 et L1VolatilityRegimeColor.mq5


        Les figures 18 à 20 présentent des exemples d’utilisation conjointe de tous les indicateurs de volatilité pour EURGBP, AUDCAD et CHFJPY.

        Fig. 18. Indicateurs de volatilité pour EURGBP

        Fig. 18. Indicateurs de volatilité pour EURGBP


        Fig. 19. Indicateurs de volatilité pour AUDCAD

        Fig. 19. Indicateurs de volatilité pour AUDCAD


        Fig. 20. Indicateurs de volatilité pour CHFJPY

        Fig. 20. Indicateurs de volatilité pour CHFJPY


        3.5. Utilisation de la tendance L1 dans les stratégies de trading

        Dans cette section, nous examinons les stratégies de trading MovingAverage, MACD, ADX et EMA avec différentes options d’application des filtres de signaux de trading.

        L’ajout de filtres aux signaux de trading permet d’améliorer les caractéristiques des systèmes de trading. Pour analyser l’efficacité des filtres dans les Expert Advisors présentés, nous enregistrerons les données de solde et de fonds (à chaque nouvelle barre) dans des fichiers séparés, puis nous utiliserons un script Python pour visualiser les résultats des systèmes de trading dans les différents modes.

        Tous les Expert Advisors présentés ont la même architecture.

        Principe général d’alignement de la tendance L1 et des signaux de trading

        • Le filtre d’ouverture (L1FilterOpen = true) ne permet d’ouvrir des transactions que dans le sens de la tendance dominante.
        • Le filtre de clôture (L1FilterClose = true) aide à conserver les positions pendant les fortes tendances et réduit les sorties prématurées lors des corrections locales.

        Paramètres d’entrée (communs à tous les 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

        Enregistrement des données de solde et de fonds dans un fichier

        Le paramètre d’entrée SaveStatistics permet d’enregistrer les valeurs actuelles de l’heure, du cours de clôture, du solde, des fonds, etc., dans un fichier, à chaque nouvelle barre, dans : terminal_data_folder\Tester\Agent-127.0.0.1-3000\MQL5\Files.

        La fonction d’enregistrement des données est appelée dans OnTick() et dépend de la valeur du paramètre d’entrée bool SaveStatistics.

        //+------------------------------------------------------------------+
        //| 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();
          }

        Le préfixe du nom du fichier enregistré dépend de la combinaison de L1FilterOpen et L1FilterClose.

        Le nom du fichier dépend de la stratégie et du symbole, et il est construit dans la fonction d’initialisation de l’Expert Advisor :

        //+------------------------------------------------------------------+
        //| 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;
          }

        Application des filtres de signaux de trading dans différents modes :

        //+------------------------------------------------------------------+
        //| 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);
          }

        Application des filtres de signaux de trading dans différents modes

        L’exécution successive de l’Expert Advisor dans le Testeur de Stratégie avec les 4 combinaisons :

        1. L1FilterOpen = false, L1FilterClose = false (trading sans filtres) ;
        2. L1FilterOpen = true, L1FilterClose = false (filtrage des entrées) ;
        3. L1FilterOpen = false, L1FilterClose = true (filtrage des sorties) ;
        4. L1FilterOpen = true, L1FilterClose = true (filtrage des entrées et des sorties).

        produit des fichiers tels que : 0_MA_EURUSD.txt, 1_MA_EURUSD.txt, 2_MA_EURUSD.txt, 3_MA_EURUSD.txt.

        Ces fichiers contiennent les données de solde et de fonds et permettent de comparer l’efficacité du filtrage des signaux de trading par alignement sur la tendance L1.

        Visualisation des données

        Pour construire les graphiques combinés, copiez les 4 fichiers dans un dossier séparé (par exemple, « C:\data\ ») et exécutez le script Python.

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


        Implémentation des filtres de signaux de trading

        Les paramètres L1TotalBars, L1FilterOpen, L1FilterClose et L1CoefLambda définissent les réglages du calcul de la tendance L1 et son utilisation pour filtrer les signaux de trading.

        Calcul de la tendance L1 et alignement sur les signaux de trading

        Dans tous les Expert Advisors, un filtrage supplémentaire est appliqué à l’aide de l’analyse de la tendance L1 :

          1. une série de cours lissée est construite à partir des L1TotalBars dernières barres ;
          2. le coefficient de croissance de la tendance, delta, est calculé comme la différence entre les deux dernières valeurs filtrées ;
            si delta > 0 — tendance haussière ; si delta < 0 — tendance baissière.
            //+------------------------------------------------------------------+
            //| 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);
              }
            

            Le paramètre L1CoefLambda est spécifié en unités de λmax, ce qui rend le filtrage robuste vis-à-vis de la volatilité et du nombre de barres.

            Filtre d’entrée en position

            Si L1FilterOpen = true :

            • les signaux BUY sont ignorés lorsque la tendance L1 est négative ;
            • les signaux SELL sont ignorés lorsque la tendance L1 est positive.

            Les transactions ne sont ouvertes que dans le sens de la tendance dominante.

            //+------------------------------------------------------------------+
            //| 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);
              }


            Filtre de sortie de position

            Si L1FilterClose = true :

            • les positions BUY ne sont pas clôturées par des signaux inverses tant que la tendance L1 reste haussière ;
            • les positions SELL ne sont pas clôturées tant que la tendance L1 reste baissière.

            Cela permet de conserver les positions dans les fortes tendances et de réduire les sorties prématurées lors des corrections locales.

            //+------------------------------------------------------------------+
            //| 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. Stratégie de trading MovingAverage

            Comme premier exemple, prenons un Expert Advisor qui exécute des transactions selon la stratégie classique de suivi de tendance basée sur la Moyenne Mobile.

            Les signaux de trading sont générés à partir du croisement du cours de clôture et de la moyenne mobile :

            • BUY — lorsque le cours de clôture croise la moyenne mobile de bas en haut ;
            • SELL — lorsque le cours de clôture croise la moyenne mobile de haut en bas.

            Les signaux sont évalués sur les deux dernières barres clôturées, ce qui élimine l’influence de la barre en cours, non terminée, et réduit les faux signaux.

            En outre, des filtres d’entrée et de sortie sont appliqués selon les réglages de la tendance L1 (L1TotalBars, L1FilterOpen, L1FilterClose, L1CoefLambda).

            Code de l’Expert Advisor MovingAverageFilteredL1.mq5 :

            //+------------------------------------------------------------------+
            //|                                      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. Méthodologie générale d’évaluation de l’efficacité du filtrage de tendance L1

            Pour évaluer l’efficacité du filtre L1, il faut :

            1. trouver le meilleur ensemble de paramètres de la stratégie de trading, celui qui donne le profit maximal.
              Il est préférable d’améliorer les signaux de trading des stratégies les plus performantes.

            2. examiner les résultats des tests pour 4 variantes d’utilisation du filtre (en définissant les paramètres L1FilterOpen / L1FilterClose) :
              • trading sans filtres ;
              • trading avec filtrage des entrées ;
              • trading avec filtrage des sorties ;
              • trading avec filtrage des entrées et des sorties.
              enregistrer les résultats dans un fichier (paramètre d’entrée SaveStatistics=true). Les fichiers obtenus doivent être copiés dans un dossier séparé (dans les exemples, C:\Data\).

            3. exécuter un script Python pour générer les graphiques combinés.

            Examinons ces étapes sur l’exemple de l’Expert Advisor MovingAverageFilteredL1.mq5, qui exécute des transactions sur EURUSD, en période H1, avec l’année 2025 comme période de test.

            Pour le fonctionnement du filtre, nous utiliserons un nombre fixe de barres pour le calcul de la tendance : L1TotalBars = 1000. Le paramètre de régularisation λ sera spécifié en unités de λmax, avec une valeur fixe L1CoefLambda = 0.2.

            Paramètres d’entrée :

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

            Dans les paramètres d’optimisation, indiquez le symbole EURUSD, la période H1 et la période de test du 01/01/2025 au 31/12/2025.

            Fig. 21. Paramètres d’optimisation de l’Expert Advisor MovingAverageFilteredL1.mq5

            Fig. 21. Paramètres d’optimisation de l’Expert Advisor MovingAverageFilteredL1.mq5


            Pour une optimisation rapide, nous utiliserons le mode « 1 minute OHLC » (cette stratégie ne travaille que sur les nouvelles barres, cette approximation est donc acceptable) et sélectionnerons l’algorithme d’optimisation « Algorithme génétique rapide » avec le critère « Solde max ».

              Fig. 22. Paramètres d’optimisation de l’Expert Advisor MovingAverageFilteredL1.mq5

            Fig. 22. Paramètres d’optimisation de l’Expert Advisor MovingAverageFilteredL1.mq5


            Notre objectif à ce stade étant de trouver les meilleurs paramètres de la stratégie de trading, nous désactivons tous les filtres et l’enregistrement des fichiers dans les paramètres d’optimisation.

            Par souci de simplicité, nous n’optimiserons qu’un seul paramètre, « MA Period », de 1 à 800, avec un pas de 1.

            Fig. 23. Résultats d’optimisation de l’Expert Advisor MovingAverageFilteredL1.mq5

            Fig. 23. Résultats d’optimisation de l’Expert Advisor MovingAverageFilteredL1.mq5


            L’optimisation du paramètre « MA Period » a pris moins d’une minute ; les résultats et la liste des meilleures valeurs sont présentés sur la Fig. 23.

            Pour un test plus précis, sélectionnez « Chaque tick basé sur des ticks réels » dans les paramètres de test :


            Fig. 24. Paramètres de test de l’Expert Advisor MovingAverageFilteredL1.mq5

            Fig. 24. Paramètres de test de l’Expert Advisor MovingAverageFilteredL1.mq5


            Lancez un test unique avec la meilleure valeur « MA Period » = 64 :

            Fig. 25. Meilleurs paramètres d’optimisation de l’Expert Advisor MovingAverageFilteredL1.mq5

            Fig. 25. Meilleurs paramètres d’optimisation de l’Expert Advisor MovingAverageFilteredL1.mq5


            Fig. 26. Résultats du test avec les meilleurs paramètres pour l’Expert Advisor MovingAverageFilteredL1.mq5

            Fig. 26. Résultats du test avec les meilleurs paramètres pour l’Expert Advisor MovingAverageFilteredL1.mq5


            Il faut maintenant lancer les tests avec enregistrement des données dans des fichiers.

            Pour ce faire, définissez SaveStatistics = true et lancez les tests avec les 4 combinaisons de filtres des signaux de trading. 

            Fig. 27. Paramètres de test pour l’enregistrement des résultats dans des fichiers pour l’Expert Advisor MovingAverageFilteredL1.mq5

            Fig. 27. Paramètres de test pour l’enregistrement des résultats dans des fichiers pour l’Expert Advisor MovingAverageFilteredL1.mq5


            Après l’exécution des tests avec les 4 combinaisons, le répertoire du testeur contiendra les fichiers : 0_MA_EURUSD.txt, 1_MA_EURUSD.txt, 2_MA_EURUSD.txt et 3_MA_EURUSD.txt.

            Ils contiennent l’heure, le cours de clôture, ainsi que le solde et les fonds pour chaque barre de l’intervalle de test.

            Créez un répertoire séparé et copiez-y les fichiers (dans cet exemple, C:\Data).


            Fig. 28. Fichiers des résultats de test de l’Expert Advisor MovingAverageFilteredL1.mq5 pour les 4 modes de filtrage

            Fig. 28. Fichiers des résultats de test pour les 4 modes de filtrage


            L’analyse des données sera effectuée à l’aide d’un script Python :

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

            Pour exécuter le script Python dans MetaEditor, cliquez sur « Compiler » (Fig. 29).


            Fig. 29. Script PlotData.py dans MetaEditor

            Fig. 29. Script PlotData.py dans MetaEditor


            Après l’exécution de PlotData.py, les graphiques suivants s’afficheront :

            1. série des cours EURUSD (cours de clôture de chaque barre) ;
            2. graphiques du solde pour tous les modes de filtrage ;
            3. graphiques des fonds pour tous les modes de filtrage ;
            4. graphiques combinés Solde + Fonds (pour évaluer la réduction du drawdown).

            Les graphiques seront également enregistrés dans C:\Data\Charts\ au format PNG.


            Fig. 30. Graphique des cours EURUSD sur la période de test

            Fig. 30. Graphique des cours EURUSD sur la période de test


            Fig. 31. Graphiques du solde de l’Expert Advisor MovingAverageFilteredL1.mq5 pour les différents modes de filtrage

            Fig. 31. Graphiques du solde de l’Expert Advisor MovingAverageFilteredL1.mq5 pour les différents modes de filtrage


            Fig. 32. Graphiques des fonds de l’Expert Advisor MovingAverageFilteredL1.mq5

            Fig. 32. Graphiques des fonds de l’Expert Advisor MovingAverageFilteredL1.mq5 pour les différents modes de filtrage


            Fig. 33. Graphiques combinés du solde et des fonds de l’Expert Advisor MovingAverageFilteredL1.mq5 pour les différents modes de filtrage

            Fig. 33. Graphiques combinés du solde et des fonds de l’Expert Advisor MovingAverageFilteredL1.mq5 pour les différents modes de filtrage


            Pour exécuter des scripts Python dans MetaEditor, installez Python (version 3.14 dans l’exemple) et indiquez son chemin dans les paramètres.


            Fig. 34. Paramètres Python dans MetaEditor

            Fig. 34. Paramètres Python dans MetaEditor


            Le script utilise les bibliothèques pandas et matplotlib. Si elles ne sont pas installées :

            pip install pandas
            pip install matplotlib


            3.5.1.2. Résultats de l’application des filtres L1 (stratégie MovingAverage)

            Les résultats (graphiques du solde, des fonds et graphiques combinés) sont présentés sur les Fig. 35 à 37.

            Couleurs :

            1. bleu (stratégie sans filtres) ;
            2. vert (filtre L1 à la clôture des positions) ;
            3. rouge (filtre L1 à l’ouverture et à la clôture) ;
            4. orange (filtre L1 à l’ouverture) ;

            Fig. 35. Graphiques du solde de l’Expert Advisor MovingAverageFilteredL1.mq5 pour les différents modes de filtrage

            Fig. 35. Graphiques du solde de l’Expert Advisor MovingAverageFilteredL1.mq5 pour les différents modes de filtrage



            Fig. 36. Graphiques des fonds de l’Expert Advisor MovingAverageFilteredL1.mq5 pour les différents modes de filtrage

            Fig. 36. Graphiques des fonds de l’Expert Advisor MovingAverageFilteredL1.mq5 pour les différents modes de filtrage


            Fig. 37. Graphiques combinés du solde et des fonds de l’Expert Advisor MovingAverageFilteredL1.mq5 pour les différents modes de filtrage

            Fig. 37. Graphiques combinés du solde et des fonds de l’Expert Advisor MovingAverageFilteredL1.mq5 pour les différents modes de filtrage



            3.5.2. Stratégie de trading MACD

            Comme autre exemple, prenons un Expert Advisor qui exécute des transactions selon les signaux d’une stratégie de trading construite sur l’indicateur MACD (Moving Average Convergence/Divergence).

            Signaux de trading

            Les signaux sont générés lorsque la ligne principale du MACD croise la ligne de signal :

            1. BUY — la ligne principale du MACD croise la ligne de signal de bas en haut.
            2. SELL — la ligne principale du MACD croise la ligne de signal de haut en bas.

            Les signaux ne sont analysés qu’à la clôture de la barre, ce qui réduit le nombre de faux déclenchements à l’intérieur de la barre.

            En outre, des filtres d’entrée et de sortie sont utilisés selon les réglages du filtre de tendance L1 (L1TotalBars, L1FilterOpen, L1FilterClose, L1CoefLambda).

            Code de l’Expert Advisor 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);
              }
            //+------------------------------------------------------------------+
            

            Les paramètres de test sont présentés sur la Fig. 38.

            Fig. 38. Paramètres du Testeur de Stratégie pour l’Expert Advisor MACDFilteredL1.mq5

            Fig. 38. Paramètres du Testeur de Stratégie pour l’Expert Advisor MACDFilteredL1.mq5


            Fig. 39. Paramètres de test de l’Expert Advisor MACDFilteredL1.mq5

            Fig. 39. Paramètres de test de l’Expert Advisor MACDFilteredL1.mq5


            Fig. 40. Résultats du test de l’Expert Advisor MACDFilteredL1.mq5

            Fig. 40. Résultats du test de l’Expert Advisor MACDFilteredL1.mq5


            Fig. 41. Paramètres de test pour l’enregistrement des résultats dans des fichiers pour l’Expert Advisor MACDFilteredL1.mq5

            Fig. 41. Paramètres de test pour l’enregistrement des résultats dans des fichiers pour l’Expert Advisor MACDFilteredL1.mq5


            Après l’exécution des tests dans le Testeur de Stratégie avec les différentes configurations de filtres, des fichiers x_MACD_EURUSD.txt apparaîtront dans le répertoire du testeur.

            Ils doivent être copiés dans le dossier C:\Data\, puis le script PlotData.py doit être exécuté.


            Fig. 42. Fichiers des résultats de test de l’Expert Advisor MACDFiltered.mq5 pour les différents modes de filtrage

            Fig. 42. Fichiers des résultats de test de l’Expert Advisor MACDFiltered.mq5 pour les différents modes de filtrage



            3.5.2.1. Résultats de l’application des filtres L1 (stratégie MACD)

            Les résultats sont présentés sur les Fig. 43 à 45.

            Fig. 43. Graphiques du solde de l’Expert Advisor MACDFilteredL1.mq5 pour les différents modes de filtrage

            Fig. 43. Graphiques du solde de l’Expert Advisor MACDFilteredL1.mq5 pour les différents modes de filtrage


            Fig. 44. Graphique des fonds de l’Expert Advisor MACDFilteredL1.mq5 pour les différents modes de filtrage

            Fig. 44. Graphique des fonds de l’Expert Advisor MACDFilteredL1.mq5 pour les différents modes de filtrage


            Fig. 45. Graphiques combinés du solde et des fonds de l’Expert Advisor MACDFilteredL1.mq5 pour les différents modes de filtrage

            Fig. 45. Graphiques combinés du solde et des fonds de l’Expert Advisor MACDFilteredL1.mq5 pour les différents modes de filtrage


            3.5.3. Stratégie de trading ADX

            Comme autre exemple, prenons l’Expert Advisor ADXFilteredL1.mq5, qui implémente une stratégie de suivi de tendance basée sur l’Average Directional Movement Index (ADX).

            Les principaux signaux de trading reposent sur l’analyse des lignes +DI et −DI, ainsi que sur le niveau de l’ADX, qui caractérise la force de la tendance.

            Les signaux sont définis comme suit :

            • BUY : +DI croise −DI de bas en haut ;
            • SELL : +DI croise −DI de haut en bas.

            En outre, la valeur de l’ADX est prise en compte. Si l’ADX est inférieur au seuil ADXTrendLevel, le marché est considéré comme faiblement directionnel ou en consolidation, et le signal est ignoré.

            Les valeurs de l’indicateur sur les deux dernières barres clôturées sont utilisées, ce qui exclut l’influence de la barre en cours, non terminée, et réduit les faux signaux.

            Des filtres d’entrée et de sortie sont également appliqués selon les réglages de la tendance L1 (L1TotalBars, L1FilterOpen, L1FilterClose, L1CoefLambda).

            Le code de l’Expert Advisor ADXFilteredL1.mq5 est présenté ci-dessous.

            //+------------------------------------------------------------------+
            //|                                                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);
              }
            //+------------------------------------------------------------------+
            

            Les réglages, les paramètres et les résultats des tests sont présentés sur les Fig. 46 à 48.


            Fig. 46. Réglages du testeur pour l’Expert Advisor ADXFilteredL1.mq5

            Fig. 46. Réglages du testeur pour l’Expert Advisor ADXFilteredL1.mq5


            Fig. 47. Paramètres de test de l’Expert Advisor ADXFilteredL1.mq5

            Fig. 47. Paramètres de test de l’Expert Advisor ADXFilteredL1.mq5


            Fig. 48. Résultats du test de l’Expert Advisor ADXFilteredL1.mq5

            Fig. 48. Résultats du test de l’Expert Advisor ADXFilteredL1.mq5


            Pour le test, vous devez effectuer 4 exécutions avec les différents réglages du filtre :

            Fig. 49. Paramètres de test pour l’enregistrement des résultats dans des fichiers pour l’Expert Advisor ADXFilteredL1.mq5

            Fig. 49. Paramètres de test pour l’enregistrement des résultats dans des fichiers pour l’Expert Advisor ADXFilteredL1.mq5

            Des fichiers apparaîtront ensuite dans le répertoire du testeur ; ils doivent être copiés dans C:\Data et traités avec PlotData.py.


            Fig. 50. Fichiers des résultats de test de l’Expert Advisor ADXFiltered.mq5 avec les différents réglages du filtre

            Fig. 50. Fichiers des résultats de test de l’Expert Advisor ADXFiltered.mq5 avec les différents réglages du filtre



            3.5.3.1. Résultats de l’application des filtres L1 à la stratégie ADX

            Les résultats sont présentés sur les Fig. 51 à 53.

            Fig. 51. Graphiques du solde de l’Expert Advisor ADXFilteredL1.mq5 avec les différents modes de filtrage

            Fig. 51. Graphiques du solde de l’Expert Advisor ADXFilteredL1.mq5 avec les différents modes de filtrage


            Fig. 52. Graphiques des fonds de l’Expert Advisor ADXFilteredL1.mq5 avec les différents modes de filtrage

            Fig. 52. Graphiques des fonds de l’Expert Advisor ADXFilteredL1.mq5 avec les différents modes de filtrage


            Fig. 53. Graphiques combinés du solde et des fonds de l’Expert Advisor ADXFilteredL1.mq5 avec les différents modes de filtrage

            Fig. 53. Graphiques combinés du solde et des fonds de l’Expert Advisor ADXFilteredL1.mq5 avec les différents modes de filtrage



            3.5.4. Stratégie de trading basée sur le croisement d’EMA

            Prenons l’Expert Advisor EMAFilteredL1.mq5, qui implémente une stratégie de trading de suivi de tendance basée sur le croisement de deux moyennes mobiles exponentielles (Exponential Moving Average, EMA).

            La stratégie utilise deux moyennes mobiles :

            1. FastEMA — moyenne mobile exponentielle rapide ;
            2. SlowEMA — moyenne mobile exponentielle lente.

            Les signaux de trading sont générés comme suit :

            • BUY : lorsque l’EMA rapide croise l’EMA lente de bas en haut ;
            • SELL : lorsque l’EMA rapide croise l’EMA lente de haut en bas.

            L’analyse utilise les valeurs des indicateurs sur les deux dernières barres clôturées, ce qui élimine l’influence de la barre en cours, non terminée, et réduit le nombre de faux signaux.

            En outre, des filtres d’entrée et de sortie sont appliqués conformément aux réglages du filtre de tendance L1 (L1TotalBars, L1FilterOpen, L1FilterClose, L1CoefLambda).

            Le code de l’Expert Advisor EMAFilteredL1.mq5 est présenté ci-dessous.

            //+------------------------------------------------------------------+
            //|                                                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);
              }
            //+------------------------------------------------------------------+

            Les réglages, les paramètres et les résultats de test de l’Expert Advisor EMAFilteredL1.mq5 sont présentés sur les Fig. 54 à 56.

            Fig. 54. Réglages du testeur pour l’Expert Advisor EMAFilteredL1.mq5

            Fig. 54. Réglages du testeur pour l’Expert Advisor EMAFilteredL1.mq5


            Fig. 55. Paramètres de test de l’Expert Advisor EMAFilteredL1.mq5

            Fig. 55. Paramètres de test de l’Expert Advisor EMAFilteredL1.mq5


            Fig. 56. Résultats du test de l’Expert Advisor EMAFilteredL1.mq5

            Fig. 56. Résultats du test de l’Expert Advisor EMAFilteredL1.mq5


            Pour analyser l’efficacité des filtres L1, il faut exécuter successivement l’Expert Advisor dans le testeur avec les différents modes de filtrage :

            Fig. 57. Paramètres de test pour l’enregistrement des résultats dans des fichiers pour l’Expert Advisor EMAFilteredL1.mq5

            Fig. 57. Paramètres de test pour l’enregistrement des résultats dans des fichiers pour l’Expert Advisor EMAFilteredL1.mq5


            Des fichiers seront alors créés dans le dossier du testeur ; il faut les copier dans le répertoire indiqué dans le script Python PlotData.py, puis exécuter ce script avec les réglages : symbol = "EURUSD", name_strategy = "EMA".


            Fig. 58. Fichiers des résultats de test de l’Expert Advisor EMAFilteredL1.mq5 pour les différents modes de filtrage des signaux de trading

            Fig. 58. Fichiers des résultats de test de l’Expert Advisor EMAFilteredL1.mq5 pour les différents modes de filtrage des signaux de trading



            3.5.4.1. Résultats de l’application des filtres L1 aux signaux de trading de la stratégie EMA

            Les résultats sont présentés sur les Fig. 59 à 61.

            Fig. 59. Graphiques du solde de l’Expert Advisor EMAFilteredL1.mq5 pour les différents modes de filtrage

            Fig. 59. Graphiques du solde de l’Expert Advisor EMAFilteredL1.mq5 pour les différents modes de filtrage


            Fig. 60. Graphiques des fonds de l’Expert Advisor EMAFilteredL1.mq5 pour les différents modes de filtrage

            Fig. 60. Graphiques des fonds de l’Expert Advisor EMAFilteredL1.mq5 pour les différents modes de filtrage


            Fig. 61. Graphiques combinés du solde et des fonds de l’Expert Advisor EMAFilteredL1.mq5 pour les différents modes de filtrage

            Fig. 61. Graphiques combinés du solde et des fonds de l’Expert Advisor EMAFilteredL1.mq5 pour les différents modes de filtrage




            3.5.5. Synthèse de l’utilisation du filtre L1 dans les stratégies de trading MovingAverage, MACD, ADX et EMA

            Dans les exemples considérés, les stratégies de trading ont été testées sur la paire de devises EURUSD, en période H1, sur l’année 2025 (Fig. 62).

            Fig. 62. Graphique des cours EURUSD sur la période de test (2025, H1, cours de clôture)

            Fig. 62. Graphique des cours EURUSD sur la période de test (2025, H1, cours de clôture)


            Fig. 63. Graphiques du solde des stratégies Moving Average, MACD, ADX et EMA pour les différents modes de filtrage

            Fig. 63. Graphiques du solde des stratégies Moving Average, MACD, ADX et EMA pour les différents modes de filtrage


            L’analyse des stratégies Moving Average, MACD, ADX et EMA a montré que les meilleurs résultats sont obtenus en appliquant le filtrage L1 lors de la clôture des positions (en vert sur les graphiques). L’utilisation du filtre en sortie supprime efficacement les retournements dus au bruit et les faux signaux, ce qui permet de conserver les positions dans le sens d’une tendance stable. Il en résulte une hausse du profit et du Facteur de Profit, ainsi qu’une baisse du drawdown maximal.

            L’utilisation du filtrage L1 lors de l’ouverture des positions (en orange) s’est révélée moins efficace : le filtrage supplémentaire limite le nombre d’entrées et fait manquer une partie des mouvements rentables, sans amélioration proportionnelle de la qualité des transactions.


            Fig. 64. Graphiques du solde et des fonds des stratégies Moving Average, MACD, ADX et EMA pour les différents modes de filtrage

            Fig. 64. Graphiques du solde et des fonds des stratégies Moving Average, MACD, ADX et EMA pour les différents modes de filtrage


            Ainsi, le filtrage L1 des signaux de trading lors de la clôture des positions accroît la stabilité du système de trading, réduit la sensibilité aux fluctuations de cours à court terme et améliore le rapport profit/risque. Par rapport aux moyennes mobiles classiques, le filtre L1 distingue mieux les corrections temporaires des véritables retournements de tendance, ce qui permet d’utiliser plus efficacement les stratégies de suivi de tendance.

            Il convient de prêter attention au comportement des courbes de solde et de fonds lorsque l’on exécute des transactions avec des signaux alignés sur le filtre de tendance L1 (Fig. 64). Lorsque l’on exécute des transactions dans le sens de la tendance, les fonds sont souvent supérieurs au solde, ce qui améliore nettement les métriques de risque et réduit le drawdown. Par conséquent, l’alignement sur la tendance améliore aussi les caractéristiques des systèmes de trading (baisse du drawdown et des risques).

            En outre, lorsque les signaux de trading sont alignés sur la tendance L1, le nombre de transactions diminue tandis que leur qualité augmente, ce qui a également un effet positif sur les caractéristiques statistiques globales des stratégies de trading.


            N° Stratégie Profit Total Net, USD % Buy and Hold
            1  Buy and Hold 1 363,8 100 %
            2  MovingAverage (sans filtre) 1 001,03 73,4 %
            3  MovingAverage (filtre L1 en entrée) 107,65 7,89 %
            4  MovingAverage (filtre L1 en sortie) 1 342,5 98,43 %
            5  MovingAverage (L1 en entrée + sortie) 986,16 72,31 %
            6  MACD (sans filtre) 997,79 73,16 %
            7  MACD (filtre L1 en entrée) 140,13 10,27 %
            8  MACD (filtre L1 en sortie) 1 359,52 99,69 %
            9  MACD (L1 en entrée + sortie) 697,54 51,15 %
            10  ADX (sans filtre) 791,99 58,07 %
            11  ADX (filtre L1 en entrée) -50,9 -3,73 %
            12  ADX (filtre L1 en sortie) 940,39 68,95 %
            13  ADX (L1 en entrée + sortie) 430,05 31,53 %
            14  EMA (sans filtre) 957,3 70,19 %
            15  EMA (filtre L1 en entrée) -173,35 -12,71 %
            16  EMA (filtre L1 en sortie) 1 258,99 92,31 %
            17  EMA (L1 en entrée + sortie) -131,41 -9,64 %

            Tableau 4. Profit total obtenu avec le filtre L1 dans les stratégies MovingAverage, MACD, ADX et EMA, comparé à la stratégie Buy and Hold


            D’après le tableau 4, l’utilisation du filtre L1 à la clôture des positions a amélioré la rentabilité de toutes les stratégies.

            Si l’on prend le résultat de la stratégie Buy and Hold (1 363,8 USD) comme 100 % du mouvement de tendance complet, on obtient :

            1. le profit de Moving Average est passé de 73,4 % à 98,43 % ;
            2. le profit de MACD est passé de 73,16 % à 99,69 % ;
            3. le profit d’ADX est passé de 58,07 % à 68,5 % ;
            4. le profit d’EMA est passé de 70,19 % à 92,31 %.

            Comme on le voit, l’utilisation du filtre L1 a permis aux stratégies Moving Average, MACD et EMA d’augmenter leur profit de 22 à 26 %, en captant l’essentiel du mouvement de tendance (98,43 %, 99,69 % et 92,31 %) et en se rapprochant du résultat de la stratégie Buy and Hold. Le profit de la stratégie ADX a augmenté de 10 %.

            Dans les exemples, nous avons considéré des stratégies avec les paramètres donnant les valeurs de solde les plus élevées (c’est-à-dire parmi les meilleures solutions possibles). Elles sont mises en évidence en bleu. Les résultats montrent que même ces solutions, les plus rentables, peuvent encore être améliorées par un filtrage supplémentaire des signaux de trading grâce à l’alignement sur la tendance L1. Certaines stratégies ne se sont que légèrement améliorées (pour ADX, les courbes vertes sont proches des bleues, ce qui indique une proximité avec la solution optimale en termes de solde). La qualité des signaux de trading d’une stratégie (le caractère optimal des paramètres choisis) peut être jugée à l’aune de l’amélioration apportée par un tel filtrage L1.

            Il faut noter que, dans ce cas, le marché EURUSD considéré présentait une forte tendance (Fig. 62). Pour d’autres régimes de marché et instruments, les résultats seront différents. En outre, la tendance L1 a été construite sur la période H1 avec un paramètre de régularisation λ = 0,2·λmax. Pour d’autres instruments et périodes, les valeurs appropriées de ce coefficient peuvent être estimées à l’aide des indicateurs de tendance L1.


            Conclusion

            Le filtrage de tendance L1 a prouvé son utilité pratique en tant qu’outil permettant de séparer le bruit local des véritables changements de tendance.

            La méthode produit une tendance linéaire par morceaux avec des points de rupture automatiques et une échelle de réglage pratique via λmax, ce qui élimine le problème de l’ajustement manuel des paramètres.

            Pour l’intégration pratique, une boîte à outils complète a été fournie : des fonctions de calcul de λmax et du filtre L1, trois indicateurs (L1Trend, L1TrendSlope, L1TrendSlopeSign), sept indicateurs de volatilité basés sur la tendance L1 (L1Volatility, L1VolatilitySmoothed, L1VolatilityAbsolute, L1VolatilityNormalized, L1VolatilityNormalizedSmoothed, L1VolatilityRegime, L1VolatilityRegimeColor), des modèles d’Expert Advisors et un protocole de test reproductible (quatre modes : sans filtre, filtre en entrée, filtre en sortie, les deux filtres ; enregistrement des résultats et script Python de visualisation).

            Notons également que le filtre de tendance L1 peut être utilisé pour l’étiquetage des données en apprentissage automatique (Machine Learning, ML). Ainsi, dans l’article « Développer des stratégies de trading de tendance avec l’apprentissage automatique », la tendance est déterminée à l’aide des dérivées des cours lissés par le filtre de Savitzky–Golay. Une approche similaire peut être mise en œuvre avec le filtrage L1, où la tendance est approximée par des fonctions linéaires par morceaux et où la force de la tendance sur chaque segment est naturellement liée à la pente de ce segment.


            Recommandations pratiques :

            • Utilisez une régularisation relative : λ = coef_lambda_max · λmax. Pour la plupart des tâches, utilisez un coef compris entre 0,04 et 0,25 ; pour plus de détail, ≈0,02–0,04 ; pour une approximation grossière et la détection de régime, ≈0,12–0,25.
            • Dans la plupart des cas, le filtre L1 est le plus efficace lorsqu’il est appliqué à la clôture des positions (conservation des tendances rentables et réduction des sorties prématurées). L’appliquer aux entrées réduit souvent le nombre de transactions sans amélioration proportionnelle de leur qualité.
            • Pour analyser la tendance actuelle, utilisez une règle simple : delta = x_filtered[last] − x_filtered[last−1]. Le signe de delta indique la direction de la tendance L1 dominante.

            Limite : l’effet dépend de l’instrument, de la période et du régime de marché ; une validation sur des données historiques avec les métriques choisies est nécessaire.

            Les modules MQL5 et le protocole de test proposés permettent de tester rapidement des hypothèses et de choisir les paramètres de travail d’un système de trading donné.

            Tous les codes de l’article sont également disponibles dans le projet public « MQL5\Shared Projects\L1Trend ».


            Exemples

            Type Fichier Description
            Script MQL5\Scripts\TestL1Trend.mq5
            Script de test pour le calcul de la tendance L1 sur des données simulées (marche aléatoire)
            Script MQL5\Scripts\TestL1TrendFloatDouble.mq5 Script de test pour le calcul de la tendance L1 sur des données simulées (marche aléatoire) pour les vecteurs double et float
            Script MQL5\Scripts\TestL1TrendFilterSP500.mq5 Script de test pour le calcul de la tendance L1 sur les cotations de l’indice SP500
            Fichier de données MQL5\Files\snp500.txt
            Fichier de données pour le script de test (logarithme de la série des cours de l’indice SP500)
            Script MQL5\Scripts\TestScalingBrownianMotion.mq5 Script de calcul de la dépendance en loi de puissance de λmax pour le mouvement brownien
            Script MQL5\Scripts\TestScalingSymbol.mq5 Script de calcul de la dépendance en loi de puissance de λmax pour la série des cours d’un symbole donné
            Indicateur MQL5\Indicators\L1TrendFilter.mq5 Indicateur de calcul de la tendance L1
            Indicateur
            MQL5\Indicators\L1TrendFilter_Slope.mq5 Indicateur de calcul du taux de variation de la tendance L1
            Indicateur
            MQL5\Indicators\L1TrendFilter_SlopeSign.mq5 Indicateur de calcul du signe de la variation de la tendance L1
            Indicateur
            MQL5\Indicators\L1Volatility.mq5 Indicateur de calcul de la volatilité résiduelle (différence entre les cours de clôture et la valeur de la tendance L1)
            Indicateur
            MQL5\Indicators\L1VolatilitySmoothed.mq5 Indicateur de calcul de la volatilité résiduelle lissée
            Indicateur
            MQL5\Indicators\L1VolatilityAbsolute.mq5 Indicateur de calcul de la volatilité absolue
            Indicateur
            MQL5\Indicators\L1VolatilityNormalized.mq5 Indicateur de calcul de la volatilité normalisée à l’aide de l’ATR (Average True Range) et de la tendance L1
            Indicateur
            MQL5\Indicators\L1VolatilityNormalizedSmoothed.mq5 Indicateur de calcul de la volatilité normalisée lissée
            Indicateur
            MQL5\Indicators\L1VolatilityRegime.mq5 Indicateur de détection du régime de marché
            Indicateur
            MQL5\Indicators\L1VolatilityRegimeColor.mq5 Version couleur de l’indicateur de détection du régime de marché
            Expert Advisor MQL5\Experts\MovingAverageFilteredL1.mq5 Expert Advisor de trading basé sur la stratégie Moving Average avec filtre L1
            Expert Advisor
            MQL5\Experts\MACDFilteredL1.mq5 Expert Advisor de trading basé sur la stratégie MACD avec filtre L1
            Expert Advisor MQL5\Experts\ADXFilteredL1.mq5 Expert Advisor de trading basé sur la stratégie ADX avec filtre L1
            Expert Advisor MQL5\Experts\EMAFilteredL1.mq5 Expert Advisor de trading basé sur le croisement de deux EMA avec filtre L1
            Script Python MQL5\Scripts\PlotData.py Script Python d’analyse de l’efficacité de l’application du filtre L1 aux signaux de trading

            Tableau 5. Description des programmes utilisés dans l’article



            Traduit du russe par MetaQuotes Ltd.
            Article original : https://www.mql5.com/ru/articles/21142

            Derniers commentaires | Aller à la discussion (9)
            Quantum
            Quantum | 20 avr. 2026 à 20:12
            Renat Akhtyamov #:

            En gros, ça donne quelque chose comme ça :

            La segmentation en tendances dépend fortement du paramètre de régularisation lambda : plus lambda est petit, plus les tendances détectées sont courtes.
            Dans les exemples considérés, des valeurs fixes de lambda ont été utilisées, exprimées en unités lambda = 0,2 * lambda_max. Le calcul en unités lambda_max permet en partie de s'adapter aux données. La valeur même de lambda_max dépend de la géométrie de la série (des amplitudes relatives), c'est-à-dire de la volatilité.

            Il faut garder à l’esprit qu’une tendance comporte différentes phases et possède son propre cycle de vie. C’est pourquoi il faut un mécanisme permettant de s’adapter à la tendance actuelle, c’est-à-dire de gérer lambda d’une manière ou d’une autre et de trouver la segmentation optimale de la tendance — cette tâche n’est pas encore résolue.

            Si la stratégie elle-même ne génère pas de bénéfices sur la période considérée, le filtre ne pourra pas non plus y remédier.
            Les meilleurs résultats devraient être obtenus sur un marché parfaitement tendanciel ; voici un exemple : EURUSD, 2025, H1 (meilleurs paramètres : MovingAverage period=61).

            EURUSD

            Options de test

            Paramètres du testeur

            Résultats des tests sans filtre

            Filtre L1 Close

            Filtre de fermeture L1

            On constate ici que le filtre de sortie a permis d’augmenter les bénéfices sur la partie de tendance.


            Variante de la même stratégie avec ajout de positions lors des corrections :

            Tester les options avec « add »

            Tester les options avec « Ajouter des positions »

            sans ajouts :

            Avec ajouts :


            Les intervalles de marché latéral comportent de petites tendances locales et, pour les prendre correctement en compte, il faut utiliser des valeurs plus faibles pour le paramètre lambda (lorsqu'il est utilisé comme filtre de sortie).
            De plus, les meilleures valeurs des paramètres MovingAverage sur l'intervalle de marché latéral doivent être différentes. Autrement dit, les périodes optimales des moyennes sur le deuxième intervalle ont changé (mais lors de l’optimisation dans le testeur, les paramètres trouvés génèrent le plus grand profit parmi tous les autres sur l’ensemble de l’intervalle d’optimisation).

            Quantum
            Quantum | 20 avr. 2026 à 21:08

            Vérifions les résultats sur un intervalle plat avec différentes valeurs de lambda.

            Sans filtres :

            Avec un filtre de sortie lambda = 0,2 * lambda_max

            Avec un filtre lambda = 0,001 lambda_max (tendances plus fines)

            Ainsi, sur la zone de consolidation, avec lambda = 0,001 lambda_max, on parvient à améliorer le résultat obtenu sans filtres et à prendre en compte les petites tendances locales.

            Cependant, l'option avec un filtre lambda = 0,2 * lambda_max s'est ici révélée moins rentable que la stratégie sans filtres.

            Quantum
            Quantum | 20 avr. 2026 à 21:30

            Stratégie consistant à ouvrir des positions (avec différents lambda) sur des tendances locales au sein d'un mouvement latéral

            Sans filtres :


            Avec un filtre lambda = 0,2 * lambda_max et des ouvertures de positions lors des corrections :

            Avec un filtre lambda = 0,001*lambda_max et des ouvertures de positions lors des corrections :

            La variante avec un filtre lambda = 0,2*lambda_max et l'ajout de positions lors des corrections a donné de meilleurs résultats que la variante sans filtre.

            L'ajout, lors des corrections, de petites tendances locales (lambda = 0,001 * lambda_max) au sein d'un intervalle de consolidation a permis d'augmenter les bénéfices de la stratégie originale sans filtre (et d'améliorer les bénéfices de la variante avec lambda = 0,2 * lambda_max).

            Renat Akhtyamov
            Renat Akhtyamov | 21 avr. 2026 à 08:06
            Quantum #:

            Variante consistant à ajouter des positions (avec différents lambda) sur des tendances locales au sein d'un mouvement latéral

            Sans filtres :


            Avec un filtre lambda = 0,2 * lambda_max et des ouvertures de positions lors des corrections :

            Avec un filtre lambda = 0,001*lambda_max et l'ajout des corrections :

            La variante avec un filtre lambda = 0,2 * lambda_max et l'ajout de corrections a donné un meilleur résultat que la variante sans filtre.

            L'ajout, lors des corrections, de petites tendances locales (lambda = 0,001 * lambda_max) à l'intérieur de l'intervalle de consolidation a permis d'augmenter le profit de la stratégie d'origine sans filtre (et d'améliorer le profit de la variante avec lambda = 0,2 * lambda_max).

            Négociez, ne serait-ce que sur un compte démo

            La compréhension viendra avec l'expérience, après avoir enduré 10 mois de travail infructueux

            Vladimir196803
            Vladimir196803 | 25 juil. 2026 à 23:50

            Merci de nous avoir présenté ces deux nouvelles fonctions pour les vecteurs. Elles sont très utiles. Vous avez accompli un travail considérable, mais malheureusement, tous vos indicateurs présentent un fort redessinage : même sur l'historique, les pics s'inversent ou disparaissent. Ce n’est pas pour rien que vous les avez comparés au filtre de Hodrick-Prescott, qui se redessine : on pourrait également les comparer à la TMA centrée et à quelques autres. Sur les périodes plus longues, un renversement de tendance peut apparaître 300 bougies en arrière ; on peut alors passer une année à trader (et perdre son dépôt) à contre-courant de la tendance. Pour le trading réel, ni les indicateurs, ni les robots basés sur ceux-ci ne conviennent, car ils tentent de prédire l’avenir.

            En réalité, ces fonctions permettent de créer des indicateurs qui ne se redessinent pas. Dans le cas de vos indicateurs, il suffit d’attendre 1 000 bougies et de refaire le calcul à partir de la fin de la période précédente, mais cela n’est pas utile dans la pratique ; il existe des options plus intéressantes. Bonne chance dans vos recherches.

            Comment Échanger des Données : Une DLL pour MQL5 en 10 minutes Comment Échanger des Données : Une DLL pour MQL5 en 10 minutes
            Maintenant, peu de développeurs se rappellent de la façon d'écrire une DLL simple et des caractéristiques spéciales des différentes liaisons système. À l'aide de plusieurs exemples, je vais tenter de montrer l'ensemble du processus de création de la DLL simple en 10 minutes, ainsi que de discuter de certains détails techniques de notre implémentation de liaison. Je vais montrer étape par étape le processus de la création de DLL dans Visual Studio avec des exemples d'échange de différents types de variables (nombres, tableaux, chaînes, etc.). En outre, je vais vous expliquer comment protéger votre terminal client des plantages dans les DLL personnalisées.
            Du « meilleur passage » aux solutions robustes : exploration de la surface d’optimisation dans MetaTrader 5 Du « meilleur passage » aux solutions robustes : exploration de la surface d’optimisation dans MetaTrader 5
            Cet article présente une approche d’ingénieur de l’optimisation d’un Expert Advisor dans MetaTrader 5 : de la collecte de métriques personnalisées via les frames d’optimisation à l’analyse de la surface des paramètres. Un modèle EMA/RSI simple, piloté par les événements, illustre l’export CSV, le lissage et l’évaluation de la stabilité locale en Python. L’objectif est de trouver des zones de configurations stables et de les valider par une optimisation forward, afin d’obtenir une mise en œuvre fiable.
            L'Histogramme des prix (Profile du Marché) et son implémentation  en MQL5 L'Histogramme des prix (Profile du Marché) et son implémentation en MQL5
            Le Profile du Marché a été élaboré par le brillant penseur Peter Steidlmayer. Il a suggéré l’utilisation de la représentation alternative de l'information sur les mouvements de marché « horizontaux » et « verticaux » qui conduit à un ensemble de modèles complètement différent. Il a assumé qu'il existe une impulsion sous-jacente du marché ou un modèle fondamental appelé cycle d'équilibre et de déséquilibre. Dans cet article, j’examinerai l'Histogramme des Prix - un modèle simplifié de profil de marché, et décrirai son implémentation dans MQL5.
            Publiez le code de votre article sur MQL5 Algo Forge en 10 minutes : guide pas à pas Publiez le code de votre article sur MQL5 Algo Forge en 10 minutes : guide pas à pas
            Cet article est un guide pas à pas pour migrer le code d’un projet publié vers un véritable projet MQL5 Algo Forge. Vous configurerez l’environnement et l’authentification dans MetaEditor, créerez un projet dans Projets partagés, choisirez son type, organiserez les fichiers, ajouterez un README.md, vérifierez l’encodage et la compilation, enregistrerez les modifications dans Git par un commit et rendrez le dépôt public. L’article vous aide à mettre en place une structure fonctionnelle et à conserver l’historique des versions, pour le confort des lecteurs.